Skip to content

feature(cluster): Add Nebius as an inference cluster provider#340

Open
haarchri wants to merge 3 commits into
modelplaneai:mainfrom
haarchri:feautre/nebius
Open

feature(cluster): Add Nebius as an inference cluster provider#340
haarchri wants to merge 3 commits into
modelplaneai:mainfrom
haarchri:feautre/nebius

Conversation

@haarchri

@haarchri haarchri commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description of your changes

Modelplane could provision inference clusters on GKE and EKS only. This adds Nebius: a new NebiusCluster XR and compose-nebius-cluster function provision an mk8s cluster with a VPC network, a system node group, GPU node groups, and GPU clusters for InfiniBand fabrics.

InferenceCluster gains source: Nebius. Nebius has no server-side way to mint kubeconfig credentials, so the composed ProviderConfigs authenticate with the service account of the Nebius ClusterProviderConfig named default, the same identity that provisions the cluster.

ModelCache RWX storage mirrors the EFS integration on EKS. Nebius has no managed RWX storage, so the composition assembles it: a shared filesystem attached to every node group and mounted by cloud-init, Nebius's csi-mounted-fs-path CSI driver installed as a Helm release, and a modelplane-rwx-fs StorageClass pinned to that driver. The filesystem is a fixed 1024 GiB because Nebius filesystems are fixed size; there is no elastic option like EFS. The class is published on status.cache.storageClassName.

One fix reach beyond Nebius. The cluster compositions taint GPU nodes with nvidia.com/gpu, but two DaemonSets the serving stack installs tolerate nothing by default: the NFD worker. Without the NFD worker on a GPU node, the node never gets the pci-10de label the NVIDIA DRA driver's kubelet plugin schedules on, no ResourceSlices are published, and every GPU ResourceClaim stays unallocatable while all components look healthy.

Breaking change: nodePools[].fabric on InferenceCluster is now an object. It was a string enum (None, EFA):

before:

    nodePools:
    - name: gpu-h200
      fabric: EFA

with this PR:

    nodePools:
    - name: gpu-h200
      fabric:
        type: EFA 
    nodePools:
    - name: gpu-h100
      fabric:
        type: InfiniBand
        infiniband:
          fabric: fabric-2         # existing physical Nebius fabric, per region

Existing manifests using fabric: EFA must move to fabric: {type: EFA}. The infrastructure XRs (EKSCluster, NebiusCluster) are unchanged.

Open questions:

  • Did GPU scheduling through DRA ever work on EKS/GKE with tainted GPU nodes? I could not find anything that labels their GPU nodes either, so the NFD toleration may be fixing a latent bug on all three providers. Worth a check on a live EKS or GKE cluster.
  • The cache filesystem size is a constant (1024 GiB) to keep the zero-config parity with EKS. Is that acceptable for now, with a spec knob as a later backward-compatible addition if someone needs more?
  • driversPreset keeps the legacy cuda12/cuda12.4 enum values for older kubernetes versions (1.31/1.32). Keep them, or drop and support only what current versions accept? Whats supported on EKS / GKE ?
  • On a dev control plane, crossplane project run installs provider-helm before prerequisites.yaml can be applied, so the ImageConfig's runtime config never attaches and the DeploymentRuntimeConfig has to be patched onto the Provider by hand... something i doing wrong ?! #:dancers:

Validation:

Validated end to end on a real Nebius project: mk8s cluster and H100 node group up, serving stack installed, a 0.5B model served through the gateway, then a 72B AWQ model loaded from a ModelCache staged on the composed shared filesystem, created after the cluster existed.

  • Create in Nebius inside a Project an ServiceAccount with Edit Role
  • Create Keys
nebius iam auth-public-key generate \                                             
  --parent-id project-0000000000 --service-account-id serviceaccount-0000000000 \
  --output raw.json

jq '{
  private_key:   ."subject-credentials"."private-key",
  public_key_id: ."subject-credentials".kid,
  account_id:    ."subject-credentials".sub
}' raw.json > credentials.json
  • Create credentials JSON

  • Create Secret + ClusterProviderConfig:

apiVersion: v1
kind: Secret
metadata:
  name: nebius-credentials
  namespace: crossplane-system
type: Opaque
stringData:
  credentials.json: |
    # content for credentials.json
    {
      "private_key": "<insert-me>",
      "public_key_id": "<insert-me>",
      "account_id": "<insert-me>"
    }
---
apiVersion: nebius.m.upbound.io/v1beta1
kind: ClusterProviderConfig
metadata:
  name: default
  namespace: crossplane-system
spec:
  identity:
    type: ServiceAccount
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: nebius-credentials
      key: credentials.json
  projectID: <insert-me>
  • Create all needed resources to validate:
. ./nix.sh run .#run
kubectl apply -f docs/manifests/getting-started/prerequisites.yaml
kubectl apply -f examples/secret.yaml
kubectl apply -f examples/demo.yaml
cat examples/demo.yaml
---
apiVersion: modelplane.ai/v1alpha1
kind: InferenceGateway
metadata:
  name: default
  namespace: crossplane-system
spec:
  backend: Traefik
  traefik:
    version: "40.2.0"
    loadBalancer: MetalLB
    metallb:
      addressPool: "172.18.255.200-172.18.255.250"
---
apiVersion: modelplane.ai/v1alpha1
kind: InferenceClass
metadata:
  name: h100-1x
  namespace: crossplane-system
spec:
  description: "Nebius gpu-h100-sxm, 1x NVIDIA H100 80GB"
  provisioning:
    provider: Nebius
    nebius:
      platform: gpu-h100-sxm
      preset: 1gpu-16vcpu-200gb
      diskSizeGb: 200
      driversPreset: cuda13.0
      accelerator:
        type: nvidia-h100
        count: 1
  devices:
  - name: gpu
    claim: DRA
    driver: gpu.nvidia.com
    deviceClassName: gpu.nvidia.com
    count: 1
    attributes:
      architecture: { string: Hopper }
      cudaComputeCapability: { version: "9.0.0" }
    capacity:
      memory: { value: "81559Mi" }
---
apiVersion: modelplane.ai/v1alpha1
kind: InferenceCluster
metadata:
  name: nebius-demo
  namespace: crossplane-system
  labels:
    modelplane.ai/region: eu-north
spec:
  cluster:
    source: Nebius
    nebius: {}
  nodePools:
  - name: gpu-h100
    className: h100-1x
    nodeCount: 1
    minNodeCount: 1
    maxNodeCount: 1
---
apiVersion: v1
kind: Namespace
metadata:
  name: ml-team
---
apiVersion: modelplane.ai/v1alpha1
kind: ModelCache
metadata:
  name: qwen-72b-awq
  namespace: ml-team
spec:
  source: HuggingFace
  huggingFace:
    repo: Qwen/Qwen2.5-72B-Instruct-AWQ
    sizeGiB: 150
---
apiVersion: modelplane.ai/v1alpha1
kind: ModelDeployment
metadata:
  name: qwen-72b
  namespace: ml-team
spec:
  replicas: 1
  modelCacheRef:
    name: qwen-72b-awq
  engines:
  - name: qwen-72b
    members:
    - role: Standalone
      nodeSelector:
        devices:
        - name: gpu
          count: 1
          selectors:
          - cel: |
              device.capacity["gpu.nvidia.com"].memory.compareTo(quantity("70Gi")) >= 0
      template:
        spec:
          containers:
          - name: engine
            image: vllm/vllm-openai:v0.23.0
            args:
            - --model=/mnt/models
            - --served-model-name=qwen-72b
            - --max-model-len=8192
---
apiVersion: modelplane.ai/v1alpha1
kind: ModelService
metadata:
  name: qwen-72b
  namespace: ml-team
spec:
  endpoints:
  - selector:
      matchLabels:
        modelplane.ai/deployment: qwen-72b

  • testing, get the endpoint from the ModelService:
kubectl run curl --rm -i --image=curlimages/curl --restart=Never -- \
  -s http://172.18.255.200/ml-team/qwen-72b/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen-72b","messages":[{"role":"user","content":"Say hello from an H100 on Nebius, serving 72B from the model cache!"}],"max_tokens":80}'
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
{"id":"chatcmpl-40f71359-12b5-4eff-9fe8-601e6f54a239","object":"chat.completion","created":1783699725,"model":"qwen-72b","choices":[{"index":0,"message":{"role":"assistant","content":"Hello from the H100 on Nebius, serving the 72B model from the cache! It's great to be here and ready to assist you with any tasks or questions you might have. How can I help you today?","refusal":null,"annotations":null,"audio":null,"function_call":null,"tool_calls":[],"reasoning":null},"logprobs":null,"finish_reason":"stop","stop_reason":null,"token_ids":null,"routed_experts":null}],"service_tier":null,"system_fingerprint":"vllm-0.23.0-04feb8ab","usage":{"prompt_tokens":51,"total_tokens":101,"completion_tokens":50,"prompt_tokens_details":null},"prompt_logprobs":null,"prompt_token_ids":null,"prompt_text":null,"kv_transfer_params":null}pod "curl" deleted from default namespace

Fixes #

I have:

  • Read and followed Modelplane's contribution process.
  • Run nix flake check (or ./nix.sh flake check) and made sure it passes.
  • Added or updated tests covering any composition function changes.
  • Signed off every commit with git commit -s.

haarchri added 2 commits July 10, 2026 18:23
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
@haarchri

Copy link
Copy Markdown
Contributor Author

full crossplane traces:

crossplane resource trace inferencegateway 
NAME                                                                       SYNCED   READY   STATUS
InferenceGateway/default                                                   True     True    Available
├─ CustomResourceDefinition/backendtlspolicies.gateway.networking.k8s.io   -        -       
├─ CustomResourceDefinition/gatewayclasses.gateway.networking.k8s.io       -        -       
├─ CustomResourceDefinition/gateways.gateway.networking.k8s.io             -        -       
├─ CustomResourceDefinition/grpcroutes.gateway.networking.k8s.io           -        -       
├─ CustomResourceDefinition/httproutes.gateway.networking.k8s.io           -        -       
├─ CustomResourceDefinition/listenersets.gateway.networking.k8s.io         -        -       
├─ CustomResourceDefinition/referencegrants.gateway.networking.k8s.io      -        -       
├─ CustomResourceDefinition/tlsroutes.gateway.networking.k8s.io            -        -       
├─ GatewayClass/traefik                                                    -        -       
├─ Gateway/modelplane (modelplane-system)                                  -        -       
├─ ProviderConfig/modelplane-in-cluster (modelplane-system)                -        -       
├─ Release/default-b72ecd0cef22 (modelplane-system)                        True     True    Available
├─ Release/default-de6abb6b99c5 (modelplane-system)                        True     True    Available
├─ IPAddressPool/modelplane (metallb-system)                               -        -       
├─ L2Advertisement/modelplane (metallb-system)                             -        -       
├─ ClusterUsage/default-cc56808bd3de                                       -        -       
├─ Usage/default-19e11f4681c2 (modelplane-system)                          -        True    Available
├─ Usage/default-bd1f47b50da8 (modelplane-system)                          -        True    Available
├─ Usage/default-de3488cec8ad (modelplane-system)                          -        True    Available
└─ Namespace/metallb-system                                                -        -       
crossplane resource trace inferenceclass
NAME                     SYNCED   READY   STATUS
InferenceClass/h100-1x   True     True    Available
crossplane resource trace InferenceCluster
NAME                                                                                     SYNCED   READY   STATUS
InferenceCluster/nebius-demo                                                             True     True    Available
├─ NebiusCluster/nebius-demo (modelplane-system)                                         True     True    Available
│  ├─ Filesystem/nebius-demo-e902002f2f3d (modelplane-system)                            True     True    Available
│  ├─ ProviderConfig/nebius-demo-kubeconfig-147e0 (modelplane-system)                    -        -       
│  ├─ Release/nebius-demo-3fa8589e76d3 (modelplane-system)                               True     True    Available
│  ├─ Object/nebius-demo-541f1536ae50 (modelplane-system)                                True     True    Available
│  ├─ ProviderConfig/nebius-demo-kubeconfig-147e0 (modelplane-system)                    -        -       
│  ├─ Cluster/nebius-demo-02bdc01f54fe (modelplane-system)                               True     True    Available
│  ├─ NodeGroup/nebius-demo-892d3a8be885 (modelplane-system)                             True     True    Available
│  ├─ NodeGroup/nebius-demo-e1e258d4887a (modelplane-system)                             True     True    Available
│  ├─ Secret/nebius-demo-cloud-init-e84f3 (modelplane-system)                            -        -       
│  ├─ Network/nebius-demo-3a69b1507861 (modelplane-system)                               True     True    Available
│  └─ Subnet/nebius-demo-affb807f03e6 (modelplane-system)                                True     True    Available
├─ ServingStack/nebius-demo-serving-stack-82f6b (modelplane-system)                      True     True    Available
│  ├─ ProviderConfig/nebius-demo-serving-stack-82f6b-cluster-82442 (modelplane-system)   -        -       
│  ├─ Release/nebius-demo-313819037c38 (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-31bb487f4777 (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-54ae8933a995 (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-5def5fc79c7e (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-918ce427bda9 (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-e0a25a558d3f (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-ee1ec7811c0a (modelplane-system)                               True     True    Available
│  ├─ Release/nebius-demo-feb57bdabf02 (modelplane-system)                               True     True    Available
│  ├─ Object/nebius-demo-36a375eabb1f (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-76e61a41258a (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-8100b99ad0ae (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-895f082b3b33 (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-896ba5b02a64 (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-b6a9899062ae (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-caae8d3ab1e1 (modelplane-system)                                True     True    Available
│  ├─ Object/nebius-demo-ef880062dd09 (modelplane-system)                                True     True    Available
│  ├─ ProviderConfig/nebius-demo-serving-stack-82f6b-cluster-82442 (modelplane-system)   -        -       
│  ├─ Usage/nebius-demo-0f2ea163254d (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-25c309f5ad97 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-37ecd730a542 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-39fc75fbf656 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-41acd9d56c97 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-47310213f1ad (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-56b174627636 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-5ead01dc3288 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-76c5f3b101e0 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-779c8f6258af (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-7820030392d5 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-86e92d8685de (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-9b437ad4e773 (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-bb964ff0862b (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-c2b18319910e (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-e1a2b9fc4b9d (modelplane-system)                                 -        True    Available
│  ├─ Usage/nebius-demo-ef1de2f1b4a8 (modelplane-system)                                 -        True    Available
│  └─ Usage/nebius-demo-f749d4078a02 (modelplane-system)                                 -        True    Available
├─ ClusterProviderConfig/nebius-demo-cluster-kubeconfig-2c18c                            -        -       
├─ ClusterUsage/nebius-demo-70a005e9c5e7                                                 -        True    Available
└─ Usage/nebius-demo-0ecc0769df08 (modelplane-system)                                    -        True    Available
crossplane resource trace ModelCache -n ml-team
NAME                                            SYNCED   READY   STATUS
ModelCache/qwen-72b-awq (ml-team)               True     True    Available
└─ Object/qwen-72b-awq-dc237c6518ab (ml-team)   True     True    Available

crossplane resource trace modeldeployment -n ml-team
NAME                                                  SYNCED   READY   STATUS
ModelDeployment/qwen-72b (ml-team)                    True     True    Available
├─ ModelEndpoint/qwen-72b-c11c3 (ml-team)             True     True    Available
│  ├─ EndpointSlice/qwen-72b-5d4e4d73ad4a (ml-team)   -        -       
│  └─ Service/qwen-72b-8ed3a0844cf1 (ml-team)         -        -       
└─ ModelReplica/qwen-72b-c11c3 (ml-team)              True     True    Available
   ├─ Object/qwen-72b-81c1a0b1cfd9 (ml-team)          True     True    Available
   ├─ Object/qwen-72b-829cc218f125 (ml-team)          True     True    Available
   ├─ Object/qwen-72b-c2d17537389b (ml-team)          True     True    Available
   └─ Object/qwen-72b-cbe864fab835 (ml-team)          True     True    Available
crossplane resource trace modelservice -n ml-team
NAME                                           SYNCED   READY   STATUS
ModelService/qwen-72b (ml-team)                True     True    Available
└─ HTTPRoute/qwen-72b-d1bbf36eb24a (ml-team)   -        -       

Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant